// 导入需要保存的实体类 改名为 SaveData
import { User as SaveData } from '../entity/User'

import express, { Request, Response, NextFunction } from 'express'
import redis from 'xl-ts-plugins/redis'
import { TokenData as PluginTokenData, createToken as pluginCreateToken, parseToken as pluginParseToken } from 'xl-ts-plugins/token'
import { md5 } from '../utils'


// 比较保存的数据是否一致（需要根据实际情况自定义）
function checkUser(tokenUser: SaveData, user: SaveData) {
  return tokenUser && user && tokenUser.userId && user.userId && tokenUser.userName === user.userName && tokenUser.userPassword === user.userPassword
}

export type TokenData = PluginTokenData<SaveData>

// 创建 token
const createToken = (tokenData: TokenData) => {
  return pluginCreateToken<SaveData>(tokenData, global.Config.TOKEN_SECRET, global.Config.TOKEN_EXP)
}

// 解析 token
const parseToken = (token: string) => {
  return pluginParseToken<SaveData>(token, global.Config.TOKEN_SECRET)
}

/**
 * 用户 token 中间件
*/
export default class TokenUserMidware {
  static readonly TOKEN_KEY = global.Config.TOKEN_KEY || 'Authorization'


  /**
   * 获取请求中的 Token
  */
  static getToken(req: Request) {
    const headers = req.headers
    const token = headers[TokenUserMidware.TOKEN_KEY.toLowerCase()]
    return token ? token.toString() : ''
  }

  /**
   * 获取请求中的 TokenData
  */
  static getTokenData(req: Request) {
    const tokenData = req[TokenUserMidware.TOKEN_KEY] as TokenData
    return tokenData || undefined
  }

  /**
   * 创建 token、tokenData
  */
  static async createToken(saveData: SaveData) {
    const token = await createToken(saveData)
    const tokenData = await parseToken(token)

    return { token, tokenData }
  }

  /**
   * 将 user 保存进 redis
  */
  static saveTokenUser(token: string, user: SaveData) {
    const userRedisKey = md5(`${TokenUserMidware.TOKEN_KEY}_${user.userId}`)

    redis.client.lpush(userRedisKey, token)

    return redis.hmset(token, user)
  }

  /**
   * 更新某个保存进 redis 的 token user
  */
  static async updateTokenUser(token: string, updateUser: SaveData) {
    await redis.hmset(token, updateUser)
  }

  /**
   * 更新某个 user 保存进 redis 的全部 token user
  */
  static async updateAllTokenUser(updateUser: SaveData) {
    const userRedisKey = md5(`${TokenUserMidware.TOKEN_KEY}_${updateUser.userId}`)

    const tokenKeys = await redis.getList(userRedisKey)

    const saveUserList = await Promise.all(tokenKeys.map((key: string) => redis.hmget(key, SaveData)))

    await Promise.all(saveUserList.map((saveUser, index) => {
      const newUser = Object.assign(saveUser, updateUser)

      return redis.hmset(tokenKeys[index] as string, newUser)
    }))
  }

  /**
   * 将某个 user 保存进 redis 的 token 全部删除
  */
  static async removeAllTokenUser(user: SaveData) {
    const userRedisKey = md5(`${TokenUserMidware.TOKEN_KEY}_${user.userId}`)

    const tokenKeys = await redis.getList(userRedisKey)

    tokenKeys.forEach(key => redis.del(String(key)))
    redis.del(userRedisKey)
  }

  /**
   * 将 user 的某个 token 删除
  */
  static removeTokenUser(token: string) {
    return redis.del(token)
  }

  /**
   * token 处理中间件
  */
  static midware = express.Router().use(async (req: Request, res: Response, next: NextFunction) => {
    const token = TokenUserMidware.getToken(req)

    if (typeof token === 'string' && token.trim()) {
      try {
        // 查找 redis
        const redisData = await redis.hmget(token, SaveData) as TokenData
        // 解析 token
        const tokenData = await parseToken(token)

        // 当前时间
        const now = Date.now()

        // redis 是否过期
        if (now >= redisData.exp) {
          return next("登录信息过期")
        }

        // 关键信息是否不一致
        if (!checkUser(tokenData, redisData)) {
          return next('登录信息错误或不存在')
        }

        // 刷新超时时间
        redisData.exp = now + (tokenData.exp  - tokenData.iat)
        await redis.hmset(token, redisData)

        // 将数据保存进请求中
        req[TokenUserMidware.TOKEN_KEY] = redisData
        next()
      } catch (error) {
        next(error)
      }
    } else {
      next('登录信息不能为空')
    }
  })
}
